﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

namespace LowLevelGraphics
{
    /// <summary>
    /// Hu Moments
    /// </summary>
    public unsafe class Moment
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Moment"/> class.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        public Moment(UnsafeBitmap bitmap)
        {
            FindMoments(bitmap);
            FindParameters(bitmap);
            WriteParameters();
        }

        /// <summary>
        /// Gets the zeroth moment.
        /// </summary>
        /// <value>The zeroth moment.</value>
        public float ZerothMoment { get { return _fZerothMoment; } }

        /// <summary>
        /// Gets the first moment X.
        /// </summary>
        /// <value>The first moment X.</value>
        public float FirstMomentX { get { return _firstMomentX; } }

        /// <summary>
        /// Gets the first moment Y.
        /// </summary>
        /// <value>The first moment Y.</value>
        public float FirstMomentY { get { return _fFirstMomentY; } }

        /// <summary>
        /// Gets the second moment X.
        /// </summary>
        /// <value>The second moment X.</value>
        public float SecondMomentX { get { return _fSecondMomentX; } }

        /// <summary>
        /// Gets the second moment Y.
        /// </summary>
        /// <value>The second moment Y.</value>
        public float SecondMomentY { get { return _secondMomentY; } }

        /// <summary>
        /// Gets the first moment XY.
        /// </summary>
        /// <value>The first moment XY.</value>
        public float FirstMomentXY { get { return _firstMomentXY; } }

        /// <summary>
        /// Gets the length.
        /// </summary>
        /// <value>The length.</value>
        public int Length { get { return (int)_length; } }

        /// <summary>
        /// Gets the width.
        /// </summary>
        /// <value>The width.</value>
        public int Width { get { return (int)_nWidth; } }

        /// <summary>
        /// Gets the roll radians.
        /// </summary>
        /// <value>The roll radians.</value>
        public float RollRadians { get { return _fRollRad; } }

        /// <summary>
        /// Toes the radians.
        /// </summary>
        /// <param name="_fAngle">The angle.</param>
        /// <returns></returns>
        public float ToRadians(float _fAngle)
        {
            return (3.14160f / 180.0f) * _fAngle;
        }

        /// <summary>
        /// Toes the degrees.
        /// </summary>
        /// <param name="_fAngle">The angle.</param>
        /// <returns></returns>
        public float ToDegrees(float _fAngle)
        {
            return (180.0f / 3.14160f) * _fAngle;
        }

        /// <summary>
        /// Gets the information.
        /// </summary>
        /// <value>The information.</value>
        public Dictionary<string, string> Information
        {
            get { return _information; }
        }

        /// <summary>
        /// Finds the moments.
        /// </summary>
        /// <param name="_bitmap">The bitmap.</param>
        private void FindMoments(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap fastBitmap = _bitmap;
            Size size = fastBitmap.Size;

            int x = 0;
            int y = 0;

            Color pPixel = Color.Transparent;

            for (y = 0; y < size.Height; y++)
            {
                for (x = 0; x < size.Width; x++)
                {
                    pPixel = fastBitmap[x, y];
                    //Find moments - for backprojection image r, g, b intensities are the same
                    _firstMomentX += pPixel.R * x;
                    _fFirstMomentY += pPixel.R * y;
                    _fSecondMomentX += pPixel.R * x * x;
                    _secondMomentY += pPixel.R * y * y;
                    _firstMomentXY += pPixel.R * x * y;
                    _fZerothMoment += pPixel.R;
                }
            }
        }

        /// <summary>
        /// Finds the parameters.
        /// </summary>
        /// <param name="_bitmap">The bitmap.</param>
        private void FindParameters(UnsafeBitmap _bitmap)
        {
            int Xc = _bitmap.Width / 2;
            int Yc = _bitmap.Height / 2;


            float a = (_fSecondMomentX / _fZerothMoment) - (float)(Xc * Xc);
            float b = 2 * ((_firstMomentXY / _fZerothMoment) - (float)(Xc * Yc));
            float c = (_secondMomentY / _fZerothMoment) - (float)(Yc * Yc);
            float aplusc = a + c;
            float aminusc = a - c;
            float bacsqrt = (float)Math.Sqrt(b * b + aminusc * aminusc);

            _length = (float)Math.Sqrt((aplusc + bacsqrt) * 0.5);
            _nWidth = (float)Math.Sqrt((aminusc + bacsqrt) * 0.5);

            _fRollRad = (float)Math.Atan(2 * b / Math.Abs((a - c))) / 2;
        }

        /// <summary>
        /// Writes the parameters.
        /// </summary>
        private void WriteParameters()
        {
            _information.Add("Zeroth Moment", ZerothMoment.ToString());
            _information.Add("First Moment X", FirstMomentX.ToString());
            _information.Add("First Moment Y", FirstMomentY.ToString());
            _information.Add("First Moment XY", FirstMomentXY.ToString());
            _information.Add("Second Moment X", SecondMomentX.ToString());
            _information.Add("Second Moment Y", SecondMomentY.ToString());
            _information.Add("Length", Length.ToString());
            _information.Add("Width", Width.ToString());
            _information.Add("Roll", ToDegrees(RollRadians).ToString());
        }

        private float _length = 0;
        private float _nWidth = 0;
        private float _fRollRad = 0;
        private float _fZerothMoment = 0;
        private float _firstMomentX = 0;
        private float _fFirstMomentY = 0;
        private float _fSecondMomentX = 0;
        private float _secondMomentY = 0;
        private float _firstMomentXY = 0;
        private Dictionary<string, string> _information = new Dictionary<string, string>();
    }
}
